package cn.signalpha.modules.pay.granter;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.core.util.URLUtil;
import cn.signalpha.common.cache.ConfigCache;
import cn.signalpha.common.cache.ConfigKeys;
import cn.signalpha.common.dto.ConfigDTO;
import cn.signalpha.core.log.exception.ServiceException;
import cn.signalpha.core.tool.jackson.JsonUtil;
import cn.signalpha.core.tool.support.Kv;
import cn.signalpha.core.tool.utils.CollectionUtil;
import cn.signalpha.core.tool.utils.ObjectUtil;
import cn.signalpha.core.tool.utils.StringPool;
import cn.signalpha.core.tool.utils.StringUtil;
import cn.signalpha.modules.com.entity.UserOauth;
import cn.signalpha.modules.com.entity.enums.UserSourceEnum;
import cn.signalpha.modules.com.service.UserOauthService;
import cn.signalpha.modules.pay.dto.PayDTO;
import cn.signalpha.modules.pay.dto.RefundDTO;
import cn.signalpha.modules.pay.dto.SettleOrderDTO;
import cn.signalpha.modules.pay.dto.SettleUserDTO;
import cn.signalpha.modules.pay.enums.PayClientEnum;
import cn.signalpha.modules.pay.provider.IPayGranter;
import cn.signalpha.modules.pay.vo.RefundVO;
import cn.signalpha.modules.pay.vo.SettleOrderVO;
import cn.signalpha.modules.pay.vo.SettleUserVO;
import cn.signalpha.modules.shop.service.SellerService;
import com.alibaba.fastjson.JSON;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.enums.TradeType;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.WxPayApiConfig;
import com.ijpay.wxpay.WxPayApiConfigKit;
import com.ijpay.wxpay.enums.v2.PayApiEnum;
import com.ijpay.wxpay.enums.v2.ProfitSharingApiEnum;
import com.ijpay.wxpay.model.ProfitSharingModel;
import com.ijpay.wxpay.model.ReceiverModel;
import com.ijpay.wxpay.model.RefundModel;
import com.ijpay.wxpay.model.UnifiedOrderModel;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.*;

/**
 * 微信支付
 *
 * @author signalpha
 */
@Slf4j
@Component
@AllArgsConstructor
public class WxPayGranter implements IPayGranter {

	private final WxMpService wxMpService;
	private final WxMaService wxMaService;
	private final SellerService sellerService;
	private final UserOauthService userOauthService;

	@Override
	public String getPayVO(PayDTO payDTO) {
		ConfigDTO.WxPayDTO config = ConfigCache.getObject(ConfigKeys.WX_PAY, ConfigDTO.WxPayDTO.class);
		payDTO.setNotifyUrl(config.getDomain().concat(payDTO.getNotifyUrl()).concat("/wx"));
		switch (Objects.requireNonNull(PayClientEnum.instance(payDTO.getPayClient()), "支付终端错误")){
			case H5:
				return wxH5Pay(payDTO);
			case APP:
				return wxAppPay(payDTO);
			case PUBLIC:
				return wxPublicPay(payDTO);
			case MINI_APP:
				return wxMiniAppPay(payDTO);
			case NATIVE2:
				return wxNative2Pay(payDTO);
			default:
				throw new ServiceException("终端未实现");
		}
	}

	@Override
	public String verifyNotify(Map<String, String> params) {
		if (WxPayKit.verifyNotify(params, WxPayApiConfigKit.getWxPayApiConfig().getApiKey(), SignType.HMACSHA256)) {
			return WxPayKit.codeIsOk(params.get("return_code")) ? StringPool.TRUE : StringPool.FALSE;
		}
		return StringPool.FALSE;
	}

	/**
	 * 微信统一下单
	 */
	private Map<String, String> wxPushOrder(Map<String, String> params) {
		String xmlResult = WxPayApi.pushOrder(params);
		Map<String, String> resultMap = WxPayKit.xmlToMap(xmlResult);
		if(!WxPayKit.codeIsOk(resultMap.get("return_code"))){
			throw new ServiceException(resultMap.get("return_msg"));
		}
		if(!WxPayKit.codeIsOk(resultMap.get("result_code"))){
			throw new ServiceException(resultMap.get("err_code_des"));
		}
		return resultMap;
	}

	/**
     * 微信H5 支付
     */
    private String wxH5Pay(PayDTO payDTO) {
		if(StringUtil.isBlank(payDTO.getAppId())) {
			throw new ServiceException("appId不能为空");
		}

        String totalFee = new BigDecimal(payDTO.getPayMoney()).movePointRight(2).toString();
		Kv sceneInfo = Kv.create().set("h5_info", Kv.create().set("type", "Wap").set("wap_url", payDTO.getReturnUrl()).set("wap_name", ""));
        WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
        Map<String, String> params = UnifiedOrderModel
                .builder()
                .appid(payDTO.getAppId())
                .mch_id(wxPayApiConfig.getMchId())
                .nonce_str(WxPayKit.generateStr())
                .body(payDTO.getBody())
                .attach(payDTO.getNotifyParams())
                .out_trade_no(payDTO.getPayNo())
                .total_fee(totalFee)
                .spbill_create_ip(payDTO.getIp())
                .notify_url(payDTO.getNotifyUrl())
                .trade_type(TradeType.MWEB.getTradeType())
                .scene_info(JsonUtil.toJson(sceneInfo))
				.profit_sharing(payDTO.getIsSharing() == 1 ? "Y" : "N")
                .build()
                .createSign(wxPayApiConfig.getApiKey(), SignType.HMACSHA256);

		Map<String, String> resultMap = wxPushOrder(params);
		String webUrl = resultMap.get("mweb_url");
		return StringUtil.builder(webUrl, "&redirect_url=", URLUtil.encode(payDTO.getReturnUrl())).toString();
    }

	/**
	 * 微信公众号 支付
	 */
	private String wxPublicPay(PayDTO payDTO) {
//		if(StringUtil.isAnyBlank(payDTO.getCode(), payDTO.getAppId())) {
//			throw new ServiceException("appId或code不能为空");
//		}
//		String openId;
//		WxOAuth2Service oAuth2Service = wxMpService.switchoverTo(payDTO.getAppId()).getOAuth2Service();
//		try {
//			openId = oAuth2Service.getAccessToken(payDTO.getCode()).getOpenId();
//		} catch (WxErrorException e) {
//			throw new ServiceException(e.getError().getErrorMsg());
//		}
		ConfigDTO.WxMpDTO config = ConfigCache.getObject(ConfigKeys.WX_MP, ConfigDTO.WxMpDTO.class);
		UserOauth userOauth = userOauthService.getByUserIdAndSourceAndAppId(payDTO.getUserId(), UserSourceEnum.WECHAT_PUBLIC.getValue(), config.getAppId());
		if(ObjectUtil.isEmpty(userOauth)){
			throw new ServiceException("未获取到用户授权信息openId");
		}
		String totalFee = new BigDecimal(payDTO.getPayMoney()).movePointRight(2).toString();
		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
		Map<String, String> params = UnifiedOrderModel
			.builder()
			.appid(config.getAppId())
			.mch_id(wxPayApiConfig.getMchId())
			.nonce_str(WxPayKit.generateStr())
			.body(payDTO.getBody())
			.attach(payDTO.getNotifyParams())
			.out_trade_no(payDTO.getPayNo())
			.total_fee(totalFee)
			.spbill_create_ip(payDTO.getIp())
			.notify_url(payDTO.getNotifyUrl())
			.trade_type(TradeType.JSAPI.getTradeType())
			.openid(userOauth.getOpenId())
			.profit_sharing(payDTO.getIsSharing() == 1 ? "Y" : "N")
			.build()
			.createSign(wxPayApiConfig.getApiKey(), SignType.HMACSHA256);

		Map<String, String> resultMap = wxPushOrder(params);
		String prepayId = resultMap.get("prepay_id");
		return JsonUtil.toJson(WxPayKit.prepayIdCreateSign(prepayId, config.getAppId(), wxPayApiConfig.getApiKey(), SignType.HMACSHA256));
	}

	/**
	 * 微信小程序 支付
	 */
	private String wxMiniAppPay(PayDTO payDTO) {
//		if(StringUtil.isAnyBlank(payDTO.getCode(), payDTO.getAppId())) {
//			throw new ServiceException("appId或code不能为空");
//		}
//		String openId;
//		try {
//			openId = wxMaService.getUserService().getSessionInfo(payDTO.getCode()).getOpenid();
//		} catch (WxErrorException e) {
//			throw new ServiceException(e.getError().getErrorMsg());
//		}
		ConfigDTO.WxMaDTO config = ConfigCache.getObject(ConfigKeys.WX_MA, ConfigDTO.WxMaDTO.class);
		UserOauth userOauth = userOauthService.getByUserIdAndSourceAndAppId(payDTO.getUserId(), UserSourceEnum.WECHAT_MINI_APP.getValue(), config.getAppId());
		if(ObjectUtil.isEmpty(userOauth)){
			throw new ServiceException("未获取到用户授权信息openId");
		}
		String totalFee = new BigDecimal(payDTO.getPayMoney()).movePointRight(2).toString();
		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
		Map<String, String> params = UnifiedOrderModel
			.builder()
			.appid(config.getAppId())
			.mch_id(wxPayApiConfig.getMchId())
			.nonce_str(WxPayKit.generateStr())
			.body(payDTO.getBody())
			.attach(payDTO.getNotifyParams())
			.out_trade_no(payDTO.getPayNo())
			.total_fee(totalFee)
			.spbill_create_ip(payDTO.getIp())
			.notify_url(payDTO.getNotifyUrl())
			.trade_type(TradeType.JSAPI.getTradeType())
			.openid(userOauth.getOpenId())
			.profit_sharing(payDTO.getIsSharing() == 1 ? "Y" : "N")
			.build()
			.createSign(wxPayApiConfig.getApiKey(), SignType.HMACSHA256);
		Map<String, String> resultMap = wxPushOrder(params);
		log.info("微信V2支付拉起支付参数：{}，响应参数：{}", params, resultMap);
		String prepayId = resultMap.get("prepay_id");
		return JsonUtil.toJson(WxPayKit.miniAppPrepayIdCreateSign(config.getAppId(), prepayId, wxPayApiConfig.getApiKey(), SignType.HMACSHA256));
	}

	/**
	 * 微信APP 支付
	 */
	private String wxAppPay(PayDTO payDTO) {
		if(StringUtil.isBlank(payDTO.getAppId())) {
			throw new ServiceException("appId不能为空");
		}

		String totalFee =  new BigDecimal(payDTO.getPayMoney()).movePointRight(2).toString();
		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
		Map<String, String> params = UnifiedOrderModel
			.builder()
			.appid(payDTO.getAppId())
			.mch_id(wxPayApiConfig.getMchId())
			.nonce_str(WxPayKit.generateStr())
			.body(payDTO.getBody())
			.attach(payDTO.getNotifyParams())
			.out_trade_no(payDTO.getPayNo())
			.total_fee(totalFee)
			.spbill_create_ip(payDTO.getIp())
			.notify_url(payDTO.getNotifyUrl())
			.trade_type(TradeType.APP.getTradeType())
			.profit_sharing(payDTO.getIsSharing() == 1 ? "Y" : "N")
			.build()
			.createSign(wxPayApiConfig.getApiKey(), SignType.HMACSHA256);

		Map<String, String> resultMap = wxPushOrder(params);
		String prepayId = resultMap.get("prepay_id");
		return JsonUtil.toJson(WxPayKit.appPrepayIdCreateSign(payDTO.getAppId(), wxPayApiConfig.getMchId(), prepayId, wxPayApiConfig.getApiKey(), SignType.HMACSHA256));
	}

	/**
	 * 微信Native支付 模式二
	 */
	private String wxNative2Pay(PayDTO payDTO) {
		String totalFee =  new BigDecimal(payDTO.getPayMoney()).movePointRight(2).toString();
		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
		Map<String, String> params = UnifiedOrderModel
			.builder()
			.appid(wxPayApiConfig.getAppId())
			.mch_id(wxPayApiConfig.getMchId())
			.nonce_str(WxPayKit.generateStr())
			.body(payDTO.getBody())
			.attach(payDTO.getNotifyParams())
			.out_trade_no(payDTO.getPayNo())
			.total_fee(totalFee)
			.spbill_create_ip(payDTO.getIp())
			.notify_url(payDTO.getNotifyUrl())
			.trade_type(TradeType.NATIVE.getTradeType())
			.profit_sharing(payDTO.getIsSharing() == 1 ? "Y" : "N")
			.build()
			.createSign(wxPayApiConfig.getApiKey(), SignType.HMACSHA256);

		Map<String, String> resultMap = wxPushOrder(params);
		return resultMap.get("code_url");
	}

	@Override
	public RefundVO refund(RefundDTO refundDTO) {
		ConfigDTO.WxPayDTO config = ConfigCache.getObject(ConfigKeys.WX_PAY, ConfigDTO.WxPayDTO.class);
		refundDTO.setNotifyUrl(config.getDomain().concat(refundDTO.getNotifyUrl()).concat("/wx"));

		String totalFee = BigDecimal.valueOf(refundDTO.getTotalMoney()).movePointRight(2).toString();
		String refundFee = BigDecimal.valueOf(refundDTO.getRefundMoney()).movePointRight(2).toString();
		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
		Map<String, String> params = RefundModel.builder()
			.appid(wxPayApiConfig.getAppId())
			.mch_id(wxPayApiConfig.getMchId())
			.nonce_str(WxPayKit.generateStr())
			.transaction_id(refundDTO.getPayBizNo())
			.out_trade_no(refundDTO.getPayNo())
			.out_refund_no(refundDTO.getRefundNo())
			.total_fee(totalFee)
			.refund_fee(refundFee)
			.notify_url(refundDTO.getNotifyUrl())
			.build()
			.createSign(wxPayApiConfig.getApiKey(), SignType.MD5);

		byte[] certPathByte = Base64.getDecoder().decode(wxPayApiConfig.getCertP12Path());
		InputStream certPath = new ByteArrayInputStream(certPathByte);
//		String resultStr = WxPayApi.orderRefund(false, params, certPath, wxPayApiConfig.getMchId());
		String resultStr = HttpKit.getDelegate().post(WxPayApi.getReqUrl(PayApiEnum.REFUND), WxPayKit.toXml(params), certPath, wxPayApiConfig.getMchId(), "");
		Map<String, String> resultMap = WxPayKit.xmlToMap(resultStr);
		String returnCode = resultMap.get("return_code");
		if (!WxPayKit.codeIsOk(returnCode)) {
			return RefundVO.builder().code(StringPool.NO).msg(resultMap.get("return_msg")).build();
		}
		String resultCode = resultMap.get("result_code");
		if (!WxPayKit.codeIsOk(resultCode)) {
			return RefundVO.builder().code(StringPool.NO).msg(resultMap.get("err_code_des")).build();
		}
		return RefundVO.builder().code(StringPool.YES).orderBizNo(resultMap.get("refund_id")).build();
	}

	@Override
	public SettleUserVO settleUser(SettleUserDTO settleUserDTO) {
		Long userId = settleUserDTO.getTargetId();
		if(settleUserDTO.getType() == 1){
			userId = sellerService.getById(settleUserDTO.getTargetId()).getUserId();
		}
		List<Kv> userList = new ArrayList<>();
		userOauthService.listByUserId(userId).forEach(userOauth -> {
			if(StringUtil.isNoneBlank(userOauth.getAppId(), userOauth.getOpenId())){
				userList.add(Kv.create().set("appId", userOauth.getAppId()).set("openId", userOauth.getOpenId()));
			}
		});
		if(CollectionUtil.isEmpty(userList)){
			String msg = StringUtil.format(
				"添加分账接收方失败：未查询到分账方openId，类型[{}]，目标id[{}]",
				settleUserDTO.getType() == 1 ? "个人" : "商户",
				settleUserDTO.getTargetId());
			return SettleUserVO.builder().code(StringPool.NO).msg(msg).build();
		}

		for(Kv kv : userList){
			ReceiverModel receiver = ReceiverModel.builder()
				.type("PERSONAL_OPENID")
				.account(kv.getStr("openId"))
				.relation_type(settleUserDTO.getRelationType())
				.build();
			WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
			Map<String, String> params = ProfitSharingModel.builder()
				.mch_id(wxPayApiConfig.getMchId())
				.appid(kv.getStr("appId"))
				.nonce_str(WxPayKit.generateStr())
				.receiver(JSON.toJSONString(receiver))
				.build()
				.createSign(wxPayApiConfig.getApiKey(), SignType.HMACSHA256);

			String resultStr = WxPayApi.profitSharingAddReceiver(params);
			Map<String, String> resultMap = WxPayKit.xmlToMap(resultStr);
			String returnCode = resultMap.get("return_code");
			if (!WxPayKit.codeIsOk(returnCode)) {
				return SettleUserVO.builder().code(StringPool.NO).msg(resultMap.get("return_msg")).build();
			}
			String resultCode = resultMap.get("result_code");
			if (!WxPayKit.codeIsOk(resultCode)) {
				return SettleUserVO.builder().code(StringPool.NO).msg(resultMap.get("err_code_des")).build();
			}
		}
		return SettleUserVO.builder().code(StringPool.YES).msg(StringPool.EMPTY).build();
	}

	@Override
	public SettleOrderVO settleOrder(SettleOrderDTO settleOrderDTO) {
		int amount = BigDecimal.valueOf(settleOrderDTO.getAmount()).movePointRight(2).intValue();
		if(amount <= 0){
			return SettleOrderVO.builder().code(StringPool.YES).msg("小于0元无需分账").build();
		}
		Long userId = settleOrderDTO.getTargetId();
		if(settleOrderDTO.getType() == 1){
			userId = sellerService.getById(settleOrderDTO.getTargetId()).getUserId();
		}

		List<ReceiverModel> receiverModelList = new ArrayList<>();
		List<UserOauth> userOauthList = userOauthService.listByUserId(userId);
		receiverModelList.add(ReceiverModel.builder()
			.type("PERSONAL_OPENID")
			.account(userOauthList.get(0).getOpenId())
			.amount(amount)
			.description("平台入账")
			.build());

		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
		Map<String, String> params = ProfitSharingModel.builder()
			.mch_id(wxPayApiConfig.getMchId())
			.appid(userOauthList.get(0).getAppId())
			.nonce_str(WxPayKit.generateStr())
			.transaction_id(settleOrderDTO.getPayBizNo())
			.out_order_no(settleOrderDTO.getSettleNo())
			.receivers(JSON.toJSONString(receiverModelList))
			.build()
			.createSign(wxPayApiConfig.getApiKey(), SignType.HMACSHA256);

		byte[] certPathByte = Base64.getDecoder().decode(wxPayApiConfig.getCertP12Path());
		InputStream certPath = new ByteArrayInputStream(certPathByte);
//		String resultStr = WxPayApi.profitSharing(params, certPath, wxPayApiConfig.getMchId());
		String resultStr = HttpKit.getDelegate().post(WxPayApi.getReqUrl(ProfitSharingApiEnum.MULTI_PROFIT_SHARING), WxPayKit.toXml(params), certPath, wxPayApiConfig.getMchId(), "");
		Map<String, String> resultMap = WxPayKit.xmlToMap(resultStr);
		String returnCode = resultMap.get("return_code");
		if (!WxPayKit.codeIsOk(returnCode)) {
			return SettleOrderVO.builder().code(StringPool.NO).msg(resultMap.get("return_msg")).build();
		}
		String resultCode = resultMap.get("result_code");
		if (!WxPayKit.codeIsOk(resultCode)) {
			return SettleOrderVO.builder().code(StringPool.NO).msg(resultMap.get("err_code_des")).build();
		}
		return SettleOrderVO.builder().code(StringPool.YES).msg(resultMap.get("order_id")).build();
	}

}
